package org.springmvc.mybatis.pagination;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 分页查询,条件参数封装
 * <pre>
 * 使用方法:
 *   在页面中使用方法:
 *   在input标签中使用,input标签的name属性必须严格按照以下书写方式,|为分隔符
 *       &lt;input name="表名|字段名|操作符|数据类型" />
 *
 *   例如(请在注释弹出框查看):
 *       HTML部分:
 *         &lt;form id="form">
 *           &lt;input name="table|column|LIKE|" />
 *           &lt;input name="table|column|>=|DATE" />至&lt;input name="table|column|<=|DATE" />
 *         &lt;/form>
 *       JS部分:
 *         var queryConditions = GlobalUtil.getQueryParameters('form');
 *         $('#grid').datagrid('load', {queryConditions:queryConditions});
 *       Java后台接收(@RequestBody必须有):
 *         public String queryList (@RequestBody Pageable pageable);
 *       特别注意:
 *         如果需要在页面使用ajax提交,请严格按照以下格式:
 *           var queryConditions = GlobalUtil.getQueryParameters('form');
 *           var data = {
 *               page: 1,
 *               rows: 50,
 *               queryConditions: queryConditions
 *             };
 *           $.ajax({
 *             type:'POST',
 *             url:'url',
 *             data:JSON.stringify(data),
 *             dataType:"json",
 *             contentType : 'text/html; charset=UTF-8',
 *             success:function(data){},
 *             error:function(){}
 *           });
 *
 *   在后台中直接构造使用(请在注释弹出框查看):
 *       Pageable pageable = new Pageable();
 *       QueryCondition query = new QueryCondition();
 *       query.setValue(value);
 *       query.setOperator("=");
 *       pageable.getQueryConditions().add(query);
 *       List&lt;Map&lt;String, Object>> list = dao.findList(pageable);
 *
 *  SQL的写法(请在注释弹出框查看),在where条件中嵌入CommomMapper.Paging_Conditions即可:
 *      &lt;select id="select" resultType="map" parameterType="xxx.pagination.Pageable">
 *        SELECT * FROM table
 *        &lt;where>
 *        &lt;include refid="xxx.CommomMapper.Paging_Conditions" />
 *        &lt;/where>
 *        ORDER BY table.column DESC
 *      &lt;/select>
 * </pre>
 * @author tangjialin on 2016-03-07 0007.
 */
public class QueryCondition implements Serializable {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private static final long serialVersionUID = 1L;
    /** 支持的所有操作 */
    public static final String OPERATOR_CONTENT = ",=,!=,<,<=,>,>=,LIKE,LLIKE,RLIKE,IN,";
    /** 日期查询类型的处理 */
    private static Map<String, String> dateFormats = null;

    static {
        dateFormats = new HashMap<String, String>();
        dateFormats.put("DATE", "yyyy-MM-dd");
        dateFormats.put("TIMESTAMP", "yyyy-MM-dd HH:mm:ss");
        dateFormats.put("TIME", "HH:mm:ss");
    }

    /** 查询语句中的表名,如果查询语句中为表使用了别名,此处的值必须是对应别名 */
    private String tableName;
    /** 查询语句中的字段名 */
    private String columnName;
    /**
     * 操作符
     * 参考当前类中 OPERATOR_CONTENT 变量值
     * @see QueryCondition#OPERATOR_CONTENT
     */
    private String operator;
    /**
     * 数据类型
     * <pre>
     * 详见当前类中 dateFormats Map的KEY
     * 类型为 dateFormats 中列举的数据类型,必须严格按照其KEY值填写,其余情况该值可以置空
     * </pre>
     * @see QueryCondition#dateFormats
     */
    private String dataType;
    /** 查询条件的值 */
    private Object value;

    /**
     * 查询值前缀,用于对LIKE,LLIKE的支持,无须关注,需要重构时请看源码
     * @see QueryCondition#getValue()
     */
    private String valuePrefix;
    /**
     * 查询值后缀,用于对LIKE,RLIKE的支持,无须关注,需要重构时请看源码
     * @see QueryCondition#getValue()
     */
    private String valueSuffix;

    public QueryCondition() {
    }

    public QueryCondition(String columnName, String operator, Object value) {
        this.columnName = columnName;
        this.operator = operator;
        this.value = value;
    }

    public QueryCondition(String tableName, String columnName, String operator, Object value) {
        this.tableName = tableName;
        this.columnName = columnName;
        this.operator = operator;
        this.value = value;
    }

    public QueryCondition(String tableName, String columnName, String operator, String dataType, Object value) {
        this.tableName = tableName;
        this.columnName = columnName;
        this.operator = operator;
        this.dataType = dataType;
        this.value = value;
    }

    public String getTableName() {
        return tableName;
    }

    public String getColumnName() {
        return columnName;
    }

    public String getOperator() {
        return operator;
    }

    public String getDataType() {
        return dataType;
    }

    public Object getValue() {
        Object temp = value;
        if (temp != null && operator.contains("<") && temp instanceof Date) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime((Date) temp);
            calendar.add(Calendar.DAY_OF_MONTH, 1);
            temp = calendar.getTime();
        } else if (temp != null && operator.contains("IN")) {
            if (temp instanceof String) {
                String[] split = String.valueOf(temp).split(",");
                int length = split.length;
                Set<String> stringSet = new HashSet<String>(length, 1.0F);
                for (int index = 0; index < length; index++) {
                    stringSet.add("'" + split[index] + "'");
                }
                temp = stringSet;
            }
        } else {
            if (valuePrefix != null) {
                temp = valuePrefix + String.valueOf(temp);
            }
            if (valueSuffix != null) {
                temp = String.valueOf(temp) + valueSuffix;
            }
        }
        return temp;
    }

    public QueryCondition setTableName(String tableName) {
        if (logger.isDebugEnabled()) logger.debug("查询条件封装表名:" + tableName);
        if (tableName == null || tableName.isEmpty()) {
            this.tableName = null;
        } else if (validateName(tableName)) {
            this.tableName = tableName;
        } else {
            String msg = "非法的表名:" + tableName;
            if (logger.isErrorEnabled()) logger.error(msg);
            throw new IllegalArgumentException(msg);
        }
        return this;
    }

    public QueryCondition setColumnName(String columnName) {
        if (logger.isDebugEnabled()) logger.debug("查询条件封装字段名:" + columnName);
        if (columnName == null || columnName.isEmpty() || !validateName(columnName)) {
            String msg = "非法的列名:" + columnName;
            if (logger.isErrorEnabled()) logger.error(msg);
            throw new IllegalArgumentException(msg);
        }
        this.columnName = columnName;
        return this;
    }

    public QueryCondition setOperator(String operator) {
        if (logger.isDebugEnabled()) logger.debug("查询条件封装操作符:" + operator);
        if (!validateOperator(operator)) throw new IllegalArgumentException("非法的操作符");
        operator = operator.toUpperCase();
        if ("LIKE".equals(operator)) {
            valuePrefix = "%";
            valueSuffix = "%";
        } else if ("LLIKE".equals(operator)) {
            valuePrefix = "%";
        } else if ("RLIKE".equals(operator)) {
            valueSuffix = "%";
        }
        if (operator.indexOf("LIKE") != -1) {
            operator = operator.substring(operator.length() - 4, operator.length());
        }
        this.operator = operator;
        return this;
    }

    public QueryCondition setDataType(String dataType) {
        if (logger.isDebugEnabled()) logger.debug("查询条件封装数据类型:" + dataType);
        this.dataType = dataType == null || (dataType = dataType.trim()).isEmpty() ? null : dataType.toUpperCase();
        setValue(this.value);
        return this;
    }

    public QueryCondition setValue(Object value) {
        if (logger.isDebugEnabled()) logger.debug("查询条件封装查询值:" + value);
        this.value = this.dataTypeFormatValue(this.dataType, value);
        return this;
    }

    /**
     * 根据数据类型格式化查询值
     * @param dataType 数据类型
     * @param value    查询值
     * @return 返回格式化后的查询值
     */
    private Object dataTypeFormatValue(String dataType, Object value) {
        if (dataType == null || value == null) return value;
        dataType = dataType.toUpperCase();
        String dataTypeMapping = dateFormats.get(dataType);
        if (dataTypeMapping != null) {
            try {
                value = new SimpleDateFormat(dataTypeMapping).parse(String.valueOf(value));
            } catch (ParseException e) {
                if (logger.isErrorEnabled()) { logger.error(e.getMessage(), e); }
            }
        }
        return value;
    }

    /**
     * 验证表名和字段名称合法性
     * @param name 表名或字段名称
     * @return 通过验证返回true
     */
    public static boolean validateName(String name) {
        // Oracle字段名长度不能超过30个字符
        // MySql字段名和表名长度不能超过64个字符
        // 匹配所有"英文字母,数组,下划线组合"或英文字母,数组,下划线组合
        return name.length() <= 30 && name.matches("^\"\\w+\"|\\w*$");
    }

    /**
     * 验证操作符合法性
     * @param operator 操作符
     * @return 通过验证返回true
     */
    public static boolean validateOperator(String operator) {
        return operator != null && OPERATOR_CONTENT.indexOf("," + operator.toUpperCase() + ",") != -1;
    }
}
